package xx.pro.util;

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import xx.pro.config.MinIoClientConfig;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @desc 统一上传 minio 工具类
 * @author wangxianlin
 */
@Component
public class MinioUtil {
    /**
     * 日志服务
     */
    protected static Logger logger = LoggerFactory.getLogger(MinioUtil.class);

    @Resource
    MinioClient minioClient;

    @Resource
    MinIoClientConfig minIoClientConfig;

    /**
     * 查看存储bucket是否存在
     *  bucketName 需要传入桶名
     * @return boolean
     */
    @SneakyThrows
    public Boolean bucketExists(String bucketName) {
        boolean flag = false;
        flag = minioClient
                .bucketExists(BucketExistsArgs.builder()
                        .bucket(bucketName)
                        .build());
        return flag;
    }

    /**
     * 创建存储bucket
     *  bucketName 需要传入桶名
     * @return Boolean
     */
    @SneakyThrows
    public Boolean makeBucket(String bucketName) {
        // 判断 桶位是否存在
        Boolean isExist = bucketExists(bucketName);
        if (isExist) {
            logger.info("[{}] 已存在，无需重复创建", bucketName);
        } else {
            minioClient
                    .makeBucket(MakeBucketArgs.builder()
                            .bucket(bucketName)
                            .build());
        }
        return true;
    }

    /**
     * 删除存储bucket
     * bucketName 需要传入桶名
     * @return Boolean
     */
    @SneakyThrows
    public Boolean removeBucket(String bucketName) {
        Boolean isExist = bucketExists(bucketName);
        if (isExist) {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } else {
            logger.info("[{}] 不存在，无需删除", bucketName);
        }
        return true;
    }

    /**
     * 获取全部bucket
     */
    @SneakyThrows
    public List<String> getAllBuckets() {
        List<Bucket> buckets = minioClient.listBuckets();
        List<String> data = new ArrayList<>();
        buckets.forEach(item -> {
            data.add(item.name());
        });
        return data;
    }


    /**
     * 根据bucketName获取信息
     *
     * @param bucketName bucket名称
     */
    @SneakyThrows
    public Bucket getBucket(String bucketName) {
        return minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst().orElse(null);
    }


    /**
     * 文件上传
     *
     * @param file 文件
     * BucketName 需要传入桶名
     * @return Boolean
     */
    @SneakyThrows
    public String upload(MultipartFile file, String bucketName) {
        // 判断桶位名称是否为空 为空设置默认值
        if (StringUtils.isEmpty(bucketName)) {
            bucketName = minIoClientConfig.getBucketName();
        }
        // 获取原始文件名称
        String originalFilename = file.getOriginalFilename();
        // 判断问价名称是否为空
        if (StringUtils.isEmpty(originalFilename)) {
            throw new RuntimeException();
        }
        // 获取文件名 + .文件后缀
        String fileName = new Date().getTime() + originalFilename.substring(originalFilename.lastIndexOf("."));
        // 生成 日期格式文件路径
        String path = getRelativeDir(new Date());
        // 日期/文件名
        String objectName = path + "/" + fileName;
        PutObjectArgs objectArgs = PutObjectArgs
                .builder()
                .bucket(bucketName)
                .object(objectName)
                .stream(file.getInputStream(), file.getSize(), -1)
                .contentType(file.getContentType())
                .build();
        //文件名称相同会覆盖
        minioClient.putObject(objectArgs);
        return preview(objectName, bucketName);
    }

    /**
     * 预览
     * @param fileName
     * BucketName 需要传入桶名
     * @return
     */
    @SneakyThrows
    public String preview(String fileName, String bucketName) {
        // 判断桶位名称是否为空 为空设置默认值
        if (StringUtils.isEmpty(bucketName)) {
            bucketName = minIoClientConfig.getBucketName();
        }
        // 查看文件地址
        GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(bucketName).object(fileName).method(Method.GET).build();
        return minioClient.getPresignedObjectUrl(build);
    }

    /**
     * 文件下载
     * @param fileName 文件名称
     * BucketName 需要传入桶名
     * @param res response
     * @return Boolean
     */
    public void download(String fileName, String bucketName, HttpServletResponse res) {
        GetObjectArgs objectArgs = GetObjectArgs
                .builder()
                .bucket(bucketName)
                .object(fileName)
                .build();
        try (GetObjectResponse response = minioClient.getObject(objectArgs)) {
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
                while ((len = response.read(buf)) != -1) {
                    os.write(buf, 0, len);
                }
                os.flush();
                byte[] bytes = os.toByteArray();
                res.setCharacterEncoding("utf-8");
                // 设置强制下载不打开
                // res.setContentType("application/force-download");
                res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
                try (ServletOutputStream stream = res.getOutputStream()) {
                    stream.write(bytes);
                    stream.flush();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 获取访问链接，不设置有效期默认时长为7天
     *
     * @param bucketName 桶名称
     * @param objectName 文件名称
     * @param duration   时长
     * @param timeUnit   时间单位
     * @return
     * @throws Exception
     */
    public String getPreviewUrl(String bucketName, String objectName, int duration, TimeUnit timeUnit) throws Exception {
        return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .bucket(bucketName)
                .object(objectName)
                .expiry(duration, timeUnit)
                .build());
    }

    /**
     * 查看文件对象
     * BucketName 需要传入桶名
     * @return 存储bucket内文件对象信息
     */
    public List<Item> listObjects(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).build());
        List<Item> items = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                items.add(result.get());
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return items;
    }

    /**
     * 删除
     * @param fileName
     * BucketName 需要传入桶名
     * @return
     * @throws Exception
     */
    @SneakyThrows
    public boolean remove(String fileName, String bucketName) {
        // 判断桶位名称是否为空 为空设置默认值
        if (StringUtils.isEmpty(bucketName)) {
            bucketName = minIoClientConfig.getBucketName();
        }
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
        return true;
    }


    /**
     * 批量删除文件
     *
     * @param bucketName bucket
     * @param list 需要删除的文件列表
     */
    @SneakyThrows
    public void removeObjects(String bucketName, List<String> list) {
        // 判断桶位名称是否为空 为空设置默认值
        if (StringUtils.isEmpty(bucketName)) {
            bucketName = minIoClientConfig.getBucketName();
        }

        List<DeleteObject> objects = new LinkedList<>();
        // 遍历待删除的文件名
        list.forEach(objectName -> objects.add(new DeleteObject(objectName)));

        Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());

        for (Result<DeleteError> result : results) {
            DeleteError deleteError = result.get();
            System.out.println("error in deleting object:" + deleteError.objectName() + "; " + deleteError.message());
        }
    }


    public static String getRelativeDir(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH) + 1;
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        // 文件路径 upload/groupID/年月日
        String monthStr = month < 10 ? "0" + month : "" + month;
        String dayStr = day < 10 ? "0" + day : "" + day;
        String filePath = year + monthStr + dayStr;
        return filePath;
    }

}
